/****************************************************************************
 * Copyright 2010 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.greatq;

import com.gotobject.filter.predicate.Predicate;
import com.gotobject.filter.internal.ToStrings;
import com.gotobject.greatq.circuitbreaker.CircuitBreaker;
import com.gotobject.greatq.circuitbreaker.TunableCircuitBreaker;
import org.jiql.jdbc.ResultSet;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.gotobject.filter.FilterQ.from;
import static com.gotobject.filter.common.Iterables.asList;
import static com.gotobject.filter.internal.Objects.cast;
import static com.gotobject.filter.internal.Pair.of;
import static com.gotobject.filter.internal.ToStrings.str;
import static com.gotobject.greatq.conditions.PredicateMaker.eq;
import static com.gotobject.greatq.conditions.PredicateMaker.onlyBatch;
import static com.gotobject.greatq.conditions.PredicateMaker.onlySingle;
import static java.util.Collections.unmodifiableList;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
class QueryServiceImpl implements QueryService {
    private final Map<String, Query>                requests;
    private final Connection                        dbconnection;
    private final CircuitBreaker<SQLException>      circuitBreaker;

    QueryServiceImpl(Map<String, Query> requests, Connection connection){
        this.requests  = requests;
        dbconnection   = connection;
        circuitBreaker = new TunableCircuitBreaker<SQLException>();
    }

    @Override
    public List<Query> scan(Predicate<? super Query> cond) {
        return unmodifiableList(asList(from(requests.values()).where(cond).select()));
    }

    @Override
    public ResultPackage execute(Predicate<? super Query> cond) {
        final ResultPackage group = ResultPackage.empty();
        try {
            for(Query each : scan(cond)){
                final String key = each.getTag();
                if(!each.inBatchMode()){
                    group.putSingle(key).add(getQueryResult(key));
                } else {
                    final ResultPackage o = ResultPackage.prepopulated(key, getBatchResult(key));
                    group.copy(o);
                }
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        }
        return group;
    }

    @Override
    public ResultSet fetchResult(String key) {
        // only single queries...no batch queries
        final ResultPackage group = execute(eq(key).and(onlySingle()));
        return group.get(key).get(0);
    }

    @Override
    public List<ResultSet> fetchBatchResult(String key) {
        final ResultPackage group = execute(eq(key).and(onlyBatch()));
        return group.get(key);
    }

    @Override
    public Query findQuery(String key) {
        return requests.get(key);
    }

    Map<String, Query> getInternalRequests(){
        return requests;
    }

    @Override
    public Map<String, Query> getKeyQueryMap() {
        return Collections.unmodifiableMap(getInternalRequests());
    }

    /**
     * get the result of the executed query
     * @param key
     *      identifier value
     * @return
     *      a {@link ResultSet} or {@code null}.
     * @throws SQLException
     *      unable to execute the query.
     */
    ResultSet getQueryResult(String key) throws SQLException {
        circuitBreaker.callStarted();
        ResultSet result;
        try {
            result = cast(findQuery(key), SingleQueryImpl.class).runAndGet();
            circuitBreaker.callSucceeded();
        } catch (SQLException e) {
            circuitBreaker.callFailed(e);
            throw e;
        }
        return result != null ? result : null;
    }

    List<ResultSet> getBatchResult(String key) throws SQLException {
        circuitBreaker.callStarted();
        List<ResultSet> result = Collections.emptyList();
        try {
            result = cast(findQuery(key), BatchedQueryImpl.class).runAndGet();
            circuitBreaker.callSucceeded();
        } catch (SQLException e){
            circuitBreaker.callFailed(e);
        }

        return result;
    }


    public Connection getSharedConnection(){
        return dbconnection;
    }

    @Override
    public String toString() {
        return ToStrings.toString(
                this,
                of("requests", str(getInternalRequests())),
                of("alive connection", str(getSharedConnection() != null))
        );
    }
}
